package cn.sys.util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 前端传入数据的封装工具类。
 * 获取前端传入的数据req.getParameterMap()  ---> 通过工具类将其中的字段和实体类对应属性进行映射  ---> 返回存好值的对象
 * 思路：利用反射的原理
 *      1. 传入前端获取的Map数据对象，并传入需要映射的字节码文件（即对应的Class对象）
 *      2. 利用Class对象调用构造器，创建一个对应类对象
 *      3. 获取需要映射的类对象的类中的所有属性、request数据中所有的key值
 *      4. 将能对应得上的属性名字和key值找出来，用set方法赋值
 *      5. 返回符号值的对象
 */
public class ParseRequestParameterUtil {
    /**
     * 实现映射，返回一个obj对象
     * @param params    request请求参加集合
     * @param aClass    对应类的Class对象
     * @return          封装好后的对象
     */
    public static Object parseRequestParams(Map<String, String[]> params, Class aClass){
        // 1. 声明一个最终需要返回的obj对象，此对象直接使用Class对象调用构造方法创建
        Object obj = null;
        try {
            obj = aClass.getConstructor().newInstance();
            // 2. 获取类中所有属性；获取params中所有的key值；---> 建立属性和key值的对应关联（key值即为属性名，封装到Map中）
            Field[] fields = aClass.getDeclaredFields();    // 注：属性是私有的，要通过Declared的方法获取
            Set<String> keys = params.keySet();
            // 此处得到的集合中存放的key值都是能和属性名对应得上的
            Map<String, Field> keyMappingField = getExistFields(fields, keys);
            Set<String> fieldNames = keyMappingField.keySet();
            // 3. 遍历集合中的key，得到set方法，进行赋值操作
            for (String fieldName : fieldNames){
                // 3.1. 拼接set方法名，得到set方法
                String setMethodName = concatSetMethodName(fieldName);
                Field field = keyMappingField.get(fieldName);
                Method setMethod = aClass.getMethod(setMethodName, field.getType());
                // 3.2 挨个获取fieldName中的值，执行set方法，给obj中存值
                String[] strings =  params.get(fieldName);
                /*
                    注意1：这里获取到的数据都是String[]的类型，因为从前端获取到的数据，可能是一个数组或集合（如复选框）
                        所以在用set方法设置值的时候，需要判断
                    注意2：如果request中得到的数据是单个值，则需要判断其参数类型后，才能赋值，
                        因为set方法执行需要传入参数，如果参数类型不匹配会报错
                 */
                runSetMethod(setMethod, field, strings, obj);
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return obj;
    }

    /**
     * 运行set方法给obj设置值
     * @param method    set方法
     * @param field     属性对象
     * @param strings   request数据
     * @param obj       封装数据的对象
     */
    private static void runSetMethod(Method method, Field field, String[] strings, Object obj) throws InvocationTargetException, IllegalAccessException, ParseException {
        // 1. 判断是否是多个值，如果不是，就取下标0赋值给obj
        if(strings.length == 1){
            // 2. 判断参数类型
            if(field.getType().equals(Integer.class) || field.getType().equals(int.class)){
                if(strings[0] != null && !"".equals(strings[0])){
                    Integer value = Integer.valueOf(strings[0]);
                    method.invoke(obj, value);
                }
            }else if(field.getType().equals(Byte.class) || field.getType().equals(byte.class)){
                if(strings[0] != null && !"".equals(strings[0])){
                    Byte value = Byte.parseByte(strings[0]);
                    method.invoke(obj, value);
                }
            }else if(field.getType().equals(Short.class) || field.getType().equals(short.class)){
                if(strings[0] != null && !"".equals(strings[0])){
                    Short value = Short.parseShort(strings[0]);
                    method.invoke(obj, value);
                }
            }else if(field.getType().equals(Long.class) || field.getType().equals(long.class)){
                if(strings[0] != null && !"".equals(strings[0])){
                    Long value = Long.parseLong(strings[0]);
                    method.invoke(obj, value);
                }
            }else if(field.getType().equals(Double.class) || field.getType().equals(double.class)){
                if(strings[0] != null && !"".equals(strings[0])){
                    Double value = Double.parseDouble(strings[0]);
                    method.invoke(obj, value);
                }
            }else if(field.getType().equals(Float.class) || field.getType().equals(float.class)){
                if(strings[0] != null && !"".equals(strings[0])){
                    Float value = Float.parseFloat(strings[0]);
                    method.invoke(obj, value);
                }
            }else if(field.getType().equals(Boolean.class) || field.getType().equals(boolean.class)){
                if(strings[0] != null && !"".equals(strings[0])){
                    Boolean value = Boolean.parseBoolean(strings[0]);
                    method.invoke(obj, value);
                }
            }else if(field.getType().equals(Date.class)){
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                if(strings[0] != null && !"".equals(strings[0])){
                    Date value = format.parse(strings[0]);
                    method.invoke(obj, value);
                }
            }else { // 字符类型
                method.invoke(obj, strings[0]);
            }
        }else {     // 多个值的情况，暂不做处理
            System.out.println("多个值，暂未处理");
        }
    }

    /**
     * 建立属性和key值的对应关联（key值即为属性名，封装到Map中）
     * @param fields    从Class对象得到的属性对象
     * @param keys      从request参数集合中得到的key值
     * @return          返回有对应映射关系的key和field的map集合
     */
    private static Map<String, Field> getExistFields(Field[] fields, Set<String> keys){
        Map<String, Field> keyMappingField = new HashMap<>();
        // 2.1 遍历得到属性名和key值相等，存入集合（其他没有存入的字段表示没有匹配上，不做赋值处理）
        for(String key : keys){
            // 2.2 获取找到了相同名字的Field，将其对应存入集合中
            for (Field field : fields){
                if(field.getName().equals(key)){
                    keyMappingField.put(key, field);
                    break;
                }
            }
        }
        return keyMappingField;
    }

    /**
     * 拼接得到set方法名
     * @param fieldName 对应的属性名
     * @return
     */
    private static String concatSetMethodName(String fieldName){
        char[] chars = fieldName.toCharArray();
        chars[0] = (char) (chars[0] - 32);
        fieldName = new String(chars);
        return "set" + fieldName;
    }


}
